package com.atguigu.tingshu.user.service.impl;
import java.util.Date;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.album.client.AlbumInfoFeignClient;
import com.atguigu.tingshu.common.constant.PublicConstant;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.rabbit.constant.MqConst;
import com.atguigu.tingshu.common.rabbit.service.RabbitService;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.common.util.MongoUtil;
import com.atguigu.tingshu.model.user.*;
import com.atguigu.tingshu.user.client.UserInfoFeignClient;
import com.atguigu.tingshu.user.mapper.UserInfoMapper;
import com.atguigu.tingshu.user.mapper.UserPaidAlbumMapper;
import com.atguigu.tingshu.user.mapper.UserPaidTrackMapper;
import com.atguigu.tingshu.user.mapper.UserSubscribeMapper;
import com.atguigu.tingshu.user.service.UserInfoService;
import com.atguigu.tingshu.vo.album.TrackListVo;
import com.atguigu.tingshu.vo.album.TrackStatMqVo;
import com.atguigu.tingshu.vo.user.UserCollectVo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.bson.types.ObjectId;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.RsaSigner;
import org.springframework.security.jwt.crypto.sign.RsaVerifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {

    @Autowired
    private UserInfoMapper userInfoMapper;

    @Autowired
    private WxMaService wxMaService;

    @Autowired
    private RsaSigner RsaSigner;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RabbitService rabbitService;

    @Autowired
    private UserSubscribeMapper userSubscribeMapper;

    @Autowired
    private UserPaidTrackMapper userPaidTrackMapper;

    @Autowired
    private UserPaidAlbumMapper userPaidAlbumMapper;

    @Autowired
    private MongoTemplate mongoTemplate;

    @Resource
    private AlbumInfoFeignClient albumInfoFeignClient;

    @Override
    public Map<String, Object> wxLogin(String code) {
        String openId;
        HashMap<String, Object> map = new HashMap<>();
        if (!StringUtils.hasText(code))
            throw new GuiguException(201, "code 不存在");
        try {
            WxMaJscode2SessionResult wxMaJscode2SessionResult = wxMaService.getUserService().getSessionInfo(code);
            openId = wxMaJscode2SessionResult.getOpenid();
        } catch (WxErrorException e) {
            log.error("微信服务端失败{}", e.getMessage());
            throw new GuiguException(201, "微信服务端失败");
        }
        String refreshToken = redisTemplate.opsForValue().get(RedisConstant.USER_LOGIN_REFRESH_KEY_PREFIX + openId);
        if (StringUtils.hasText(refreshToken)) {
            map.put("token", refreshToken);
            return map;
        }
        // 根据openId 查询用户信息
        LambdaQueryWrapper<UserInfo> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(UserInfo::getWxOpenId, openId);
        UserInfo userInfo = userInfoMapper.selectOne(wrapper);
        if (userInfo == null) {
            // 注册
            userInfo = new UserInfo();
            userInfo.setBirthday(new Date());
            userInfo.setNickname(UUID.randomUUID().toString().replace("-", "").substring(0, 4) + "-yz-" + System.currentTimeMillis());
            userInfo.setWxOpenId(openId);
            userInfo.setAvatarUrl("https://oss.aliyuncs.com/aliyun_id_photo_bucket/default_handsome.jpg");
            userInfo.setGender(1);
            userInfo.setIsVip(0);
            userInfo.setVipExpireTime(new Date());
            userInfo.setPhone("13334445550");
            userInfo.setIntro("");
            userInfo.setStatus("1");
            int insert = userInfoMapper.insert(userInfo);
            log.info("注册用户{}", insert == 1 ? "成功" : "失败");
            // todo 向tingshu_account库中的user_account表插入一条用户数据 并初始化账户余额
            // 此处采用异步消息队列
            rabbitService.sendMessage(MqConst.EXCHANGE_USER, MqConst.ROUTING_USER_REGISTER, userInfo.getId().toString());
        }
        // 登录并获取一个token返回
        String token = getJsonWebToken(userInfo.getId(), openId);
        // accessToken 双token
        redisTemplate.opsForValue().set(RedisConstant.USER_LOGIN_KEY_PREFIX + openId, token, 10, TimeUnit.DAYS); // 测试期间 设置长一些
        // refreshToken
        redisTemplate.opsForValue().set(RedisConstant.USER_LOGIN_REFRESH_KEY_PREFIX + openId, token, 100, TimeUnit.DAYS);
        map.put("token", token);
        map.put("refreshToken", token);
        return map;
    }

    @Override
    public Map<String, Object> getNewAccessToken() {
        // 此时accessToken 过期
        /**
         * 1.需要判断 refreshToken 是否过期
         * 2.如果没有过期，则刷新accessToken，并返回
         * 3.如果过期，则返回208状态码，让前端进行重新登录操作
         */
        // 先从请求头中获取token信息
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = null;
        if (requestAttributes != null) {
            request = requestAttributes.getRequest();
        } else {
            throw new GuiguException(201, "请求头中缺少token信息");
        }
        String token = request.getHeader("token");
        if (!StringUtils.hasText(token)) {
            throw new GuiguException(201, "未登录过");
        }
        // 校验一下JWT是否被篡改 若是被篡改会抛出异常 并从中获取openId
        Jwt jwt = JwtHelper.decodeAndVerify(token, new RsaVerifier(PublicConstant.PUBLIC_KEY));

        String claims = jwt.getClaims();
        Map map = JSONObject.parseObject(claims, Map.class);
        String openId = (String) map.get("openId");
        Object userId = map.get("userId");

        String refreshTokenInRedis = redisTemplate.opsForValue().get(RedisConstant.USER_LOGIN_REFRESH_KEY_PREFIX + openId);
        if (!StringUtils.hasText(refreshTokenInRedis) && token.equals(refreshTokenInRedis)) {
            //todo 去登录
            throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
        }

        // 重新生成一个accessToken
        String jsonWebToken = getJsonWebToken(Long.parseLong(userId.toString()), openId);
        redisTemplate.opsForValue().set(RedisConstant.USER_LOGIN_KEY_PREFIX + openId, jsonWebToken, 10, TimeUnit.DAYS);
        redisTemplate.opsForValue().set(RedisConstant.USER_LOGIN_REFRESH_KEY_PREFIX + openId, jsonWebToken, 100, TimeUnit.DAYS);
        Map<String, Object> map1 = new HashMap<>();
        map1.put("token", token);

        return map1;
    }

    @Override
    public void updateUserById(UserInfo userInfo) {
        userInfoMapper.updateById(userInfo);
    }

    @Override
    public UserInfoVo getUserInfo(Long userId) {
        UserInfo userInfo = userInfoMapper.selectById(userId);
        Assert.notNull(userInfo, "用户不存在");
        UserInfoVo userInfoVo = new UserInfoVo();
        BeanUtils.copyProperties(userInfo, userInfoVo);
        return userInfoVo;
    }

    @Override
    public Boolean isSubscribe(Long albumId) {
        Long userId = AuthContextHolder.getUserId();
        UserSubscribe userSubscribe = userSubscribeMapper.selectOne(new LambdaQueryWrapper<UserSubscribe>().eq(UserSubscribe::getUserId, userId).eq(UserSubscribe::getAlbumId, albumId));
        return userSubscribe != null;
    }

    @Override
    public Map<Long, String> getUserPaidTrackInAlbum(Long albumId, Long userId) {
        LambdaQueryWrapper<UserPaidTrack> userPaidTrackLambdaQueryWrapper = new LambdaQueryWrapper<>();
        userPaidTrackLambdaQueryWrapper.eq(UserPaidTrack::getAlbumId, albumId).eq(UserPaidTrack::getUserId, userId);
        List<UserPaidTrack> userPaidTrackList = userPaidTrackMapper.selectList(userPaidTrackLambdaQueryWrapper);
        if (userPaidTrackList.isEmpty()) {
            return new HashMap<>();
        }
        Map<Long, String> map = userPaidTrackList.stream().collect(Collectors.toMap(UserPaidTrack::getTrackId, v -> "1"));
        return map;
    }

    @Override
    public Boolean getUserPaidAlbumById(Long albumId, Long userId) {
        Long count = userPaidAlbumMapper.selectCount(new LambdaQueryWrapper<UserPaidAlbum>().eq(UserPaidAlbum::getAlbumId, albumId).eq(UserPaidAlbum::getUserId, userId));
        return count > 0;
    }

    @Override
    public Boolean collect(Long trackId) {
        Long userId = AuthContextHolder.getUserId();
        Query query = Query.query(Criteria.where("userId").is(userId).and("trackId").is(trackId));
        long count = mongoTemplate.count(query, MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_COLLECT, userId));
        if(count == 0) {
            //在mongodb中添加用户收藏的信息
            UserCollect userCollect = new UserCollect();
            userCollect.setId(ObjectId.get().toString());
            userCollect.setUserId(userId);
            userCollect.setTrackId(trackId);
            userCollect.setCreateTime(new Date());
            mongoTemplate.save(userCollect, MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_COLLECT, userId));

            //发送消息，更新声音统计数量加1
            TrackStatMqVo trackStatMqVo = new TrackStatMqVo();
            trackStatMqVo.setBusinessNo(UUID.randomUUID().toString().replaceAll("-",""));
            trackStatMqVo.setTrackId(trackId);
            trackStatMqVo.setStatType(SystemConstant.TRACK_STAT_COLLECT);
            trackStatMqVo.setCount(1);
            rabbitService.sendMessage(MqConst.EXCHANGE_TRACK, MqConst.ROUTING_TRACK_STAT_UPDATE, JSONObject.toJSONString(trackStatMqVo));
            return true;
        } else {
            mongoTemplate.remove(query, MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_COLLECT, userId));
            //发送消息，更新声音统计数量减1
            TrackStatMqVo trackStatMqVo = new TrackStatMqVo();
            trackStatMqVo.setBusinessNo(UUID.randomUUID().toString().replaceAll("-",""));
            trackStatMqVo.setTrackId(trackId);
            trackStatMqVo.setStatType(SystemConstant.TRACK_STAT_COLLECT);
            trackStatMqVo.setCount(-1);
            rabbitService.sendMessage(MqConst.EXCHANGE_TRACK, MqConst.ROUTING_TRACK_STAT_UPDATE, JSONObject.toJSONString(trackStatMqVo));
            return false;
        }
    }

    @Override
    public Boolean isCollect(Long trackId) {
        Long userId = AuthContextHolder.getUserId();
        Query query = Query.query(Criteria.where("userId").is(userId).and("trackId").is(trackId));
        UserCollect userCollect = mongoTemplate.findOne(query, UserCollect.class, MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_COLLECT, userId));
        return userCollect != null;
    }

    @Override
    public IPage<UserCollectVo> findUserCollectPage(IPage<UserCollectVo> page) {
        Long userId = AuthContextHolder.getUserId();
        Criteria criteria = Criteria.where("userId").is(userId);
        Query query = new Query(criteria);
        query.with(Sort.by(Sort.Direction.DESC, "createTime"));
        Pageable pageable = PageRequest.of((int) (page.getCurrent() - 1),(int) page.getSize());
        query.with(pageable);
        List<UserCollect> userCollectList = mongoTemplate.find(query, UserCollect.class, MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_COLLECT, userId));
        Long total = mongoTemplate.count(query, UserCollect.class, MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_COLLECT, userId));
        List<Long> trackIds = userCollectList.stream().map(UserCollect::getTrackId).toList();
        Result<List<TrackListVo>> result = albumInfoFeignClient.getTrackListByIds(trackIds);
        List<TrackListVo> trackListVos = result.getData();
        if(CollectionUtils.isEmpty(trackListVos)){
            throw new GuiguException(201, "远程调用专辑微服务查询用户收藏失败");
        }
        Map<Long, TrackListVo> trackListVoMap = trackListVos.stream().collect(Collectors.toMap(TrackListVo::getTrackId, v -> v));

        List<UserCollectVo> userCollectVos = userCollectList.stream().map(userCollect -> {
            UserCollectVo userCollectVo = new UserCollectVo();
            TrackListVo trackListVo = trackListVoMap.get(userCollect.getTrackId());
            userCollectVo.setAlbumId(trackListVo.getAlbumId());
            userCollectVo.setTrackId(userCollect.getTrackId());
            userCollectVo.setCreateTime(userCollect.getCreateTime());
            userCollectVo.setTrackTitle(trackListVo.getTrackTitle());
            userCollectVo.setCoverUrl(trackListVo.getCoverUrl());
            return userCollectVo;
        }).toList();
        page.setTotal(total);
        page.setRecords(userCollectVos);
        page.setPages((total % page.getSize())== 0 ? total / page.getSize() : total / page.getSize() + 1);
        return page;
    }

    private String getJsonWebToken(Long userId, String openId) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("userId", userId);
        jsonObject.put("openId", openId);
        // 使用Spring Security JWT生成token
        Jwt jwt = JwtHelper.encode(jsonObject.toString(), RsaSigner);
        String token = jwt.getEncoded();
        return token;
    }
}
